home *** CD-ROM | disk | FTP | other *** search
/ Enter 2006 September / Enter 09 2006.iso / Internet / SpamExperts Home 1.1 / SpamExperts Home.exe / lib / spamexperts.modules / spambayes / message.pyc (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2006-07-14  |  25.1 KB  |  760 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.4)
  3.  
  4. """message.py - Core Spambayes classes.
  5.  
  6. Classes:
  7.     Message - an email.Message.Message, extended with spambayes methods
  8.     SBHeaderMessage - A Message with spambayes header manipulations
  9.     MessageInfoDB - persistent state storage for Message, using dbm
  10.     MessageInfoZODB - persistent state storage for Message, using ZODB
  11.     MessageInfoPickle - persistent state storage for Message, using pickle
  12.  
  13. Abstract:
  14.  
  15.     MessageInfoDB is a simple shelve persistency class for the persistent
  16.     state of a Message obect.  The MessageInfoDB currently does not provide
  17.     iterators, but should at some point.  This would allow us to, for
  18.     example, see how many messages have been trained differently than their
  19.     classification, for fp/fn assessment purposes.
  20.  
  21.     Message is an extension of the email package Message class, to
  22.     include persistent message information. The persistent state
  23.     currently consists of the message id, its current classification, and
  24.     its current training.  The payload is not persisted.
  25.  
  26.     SBHeaderMessage extends Message to include spambayes header specific
  27.     manipulations.
  28.  
  29. Usage:
  30.     A typical classification usage pattern would be something like:
  31.  
  32.     >>> import email
  33.     >>> # substance comes from somewhere else
  34.     >>> msg = email.message_from_string(substance, _class=SBHeaderMessage)
  35.     >>> id = msg.setIdFromPayload()
  36.  
  37.     >>> if id is None:
  38.     >>>     msg.setId(time())   # or some unique identifier
  39.  
  40.     >>> msg.delSBHeaders()      # never include sb headers in a classification
  41.  
  42.     >>> # bayes object is your responsibility
  43.     >>> (prob, clues) = bayes.spamprob(msg.asTokens(), evidence=True)
  44.  
  45.     >>> msg.addSBHeaders(prob, clues)
  46.  
  47.  
  48.     A typical usage pattern to train as spam would be something like:
  49.  
  50.     >>> import email
  51.     >>> # substance comes from somewhere else
  52.     >>> msg = email.message_from_string(substance, _class=SBHeaderMessage)
  53.     >>> id = msg.setId(msgid)     # id is a fname, outlook msg id, something...
  54.  
  55.     >>> msg.delSBHeaders()        # never include sb headers in a train
  56.  
  57.     >>> if msg.getTraining() == False:   # could be None, can't do boolean test
  58.     >>>     bayes.unlearn(msg.asTokens(), False)  # untrain the ham
  59.  
  60.     >>> bayes.learn(msg.asTokens(), True) # train as spam
  61.     >>> msg.rememberTraining(True)
  62.  
  63.  
  64. To Do:
  65.     o Suggestions?
  66. """
  67. from __future__ import generators
  68. __author__ = 'Tim Stone <tim@fourstonesExpressions.com>'
  69. __credits__ = 'Mark Hammond, Tony Meyer, all the spambayes contributors.'
  70.  
  71. try:
  72.     (True, False)
  73. except NameError:
  74.     (True, False) = (1, 0)
  75.     
  76.     def bool(val):
  77.         return not (not val)
  78.  
  79.  
  80. import os
  81. import sys
  82. import types
  83. import time
  84. import math
  85. import re
  86. import errno
  87. import shelve
  88. import warnings
  89.  
  90. try:
  91.     import cPickle as pickle
  92. except ImportError:
  93.     import pickle
  94.  
  95. import traceback
  96. import email
  97. import email.Message as email
  98. import email.Parser as email
  99. import email.Header as email
  100. import email.Generator as email
  101. from spambayes import storage
  102. from spambayes import dbmstorage
  103. from spambayes.Options import options, get_pathname_option
  104. from spambayes.tokenizer import tokenize
  105.  
  106. try:
  107.     import cStringIO as StringIO
  108. except ImportError:
  109.     import StringIO
  110.  
  111. CRLF_RE = re.compile('\\r\\n|\\r|\\n')
  112. STATS_START_KEY = 'Statistics start date'
  113. PERSISTENT_HAM_STRING = 'h'
  114. PERSISTENT_SPAM_STRING = 's'
  115. PERSISTENT_UNSURE_STRING = 'u'
  116.  
  117. class MessageInfoBase(object):
  118.     
  119.     def __init__(self, db_name = None):
  120.         self.db_name = db_name
  121.  
  122.     
  123.     def __len__(self):
  124.         return len(self.keys())
  125.  
  126.     
  127.     def get_statistics_start_date(self):
  128.         if self.db.has_key(STATS_START_KEY):
  129.             return self.db[STATS_START_KEY]
  130.         else:
  131.             return None
  132.  
  133.     
  134.     def set_statistics_start_date(self, date):
  135.         self.db[STATS_START_KEY] = date
  136.         self.store()
  137.  
  138.     
  139.     def __getstate__(self):
  140.         return self.db
  141.  
  142.     
  143.     def __setstate__(self, state):
  144.         self.db = state
  145.  
  146.     
  147.     def load_msg(self, msg):
  148.         if self.db is not None:
  149.             key = msg.getDBKey()
  150.             if not key is not None:
  151.                 raise AssertionError, 'None is not a valid key.'
  152.             
  153.             try:
  154.                 
  155.                 try:
  156.                     attributes = self.db[key]
  157.                 except pickle.UnpicklingError:
  158.                     if hasattr(self, 'dbm'):
  159.                         attributes = self.dbm[key]
  160.                     else:
  161.                         raise 
  162.                 except:
  163.                     hasattr(self, 'dbm')
  164.  
  165.             except KeyError:
  166.                 for att in msg.stored_attributes:
  167.                     if not hasattr(msg, att):
  168.                         setattr(msg, att, None)
  169.                         continue
  170.                 
  171.  
  172.             if not isinstance(attributes, types.ListType):
  173.                 if isinstance(attributes, types.TupleType):
  174.                     (msg.c, msg.t) = attributes
  175.                     return None
  176.                 elif isinstance(attributes, types.StringTypes):
  177.                     msg.t = {
  178.                         '0': False,
  179.                         '1': True }[attributes]
  180.                     return None
  181.                 else:
  182.                     print >>sys.stderr, 'Unknown message info type', attributes
  183.                     sys.exit(1)
  184.             
  185.             for att, val in attributes:
  186.                 setattr(msg, att, val)
  187.             
  188.         
  189.  
  190.     
  191.     def store_msg(self, msg):
  192.         if self.db is not None:
  193.             msg.date_modified = time.time()
  194.             attributes = []
  195.             for att in msg.stored_attributes:
  196.                 attributes.append((att, getattr(msg, att)))
  197.             
  198.             key = msg.getDBKey()
  199.             if not key is not None:
  200.                 raise AssertionError, 'None is not a valid key.'
  201.             self.db[key] = attributes
  202.             self.store()
  203.         
  204.  
  205.     
  206.     def remove_msg(self, msg):
  207.         if self.db is not None:
  208.             del self.db[msg.getDBKey()]
  209.             self.store()
  210.         
  211.  
  212.     
  213.     def keys(self):
  214.         return self.db.keys()
  215.  
  216.  
  217.  
  218. class MessageInfoPickle(MessageInfoBase):
  219.     
  220.     def __init__(self, db_name, pickle_type = 1):
  221.         MessageInfoBase.__init__(self, db_name)
  222.         self.mode = pickle_type
  223.         self.load()
  224.  
  225.     
  226.     def load(self):
  227.         
  228.         try:
  229.             fp = open(self.db_name, 'rb')
  230.         except IOError:
  231.             e = None
  232.             if e.errno == errno.ENOENT:
  233.                 self.db = { }
  234.             else:
  235.                 raise 
  236.         except:
  237.             e.errno == errno.ENOENT
  238.  
  239.         self.db = pickle.load(fp)
  240.         fp.close()
  241.  
  242.     
  243.     def close(self):
  244.         pass
  245.  
  246.     
  247.     def store(self):
  248.         fp = open(self.db_name, 'wb')
  249.         pickle.dump(self.db, fp, self.mode)
  250.         fp.close()
  251.  
  252.  
  253.  
  254. class MessageInfoDB(MessageInfoBase):
  255.     
  256.     def __init__(self, db_name, mode = 'c'):
  257.         MessageInfoBase.__init__(self, db_name)
  258.         self.mode = mode
  259.         self.load()
  260.  
  261.     
  262.     def load(self):
  263.         
  264.         try:
  265.             self.dbm = dbmstorage.open(self.db_name, self.mode)
  266.             self.db = shelve.Shelf(self.dbm)
  267.         except dbmstorage.error:
  268.             if options[('globals', 'verbose')]:
  269.                 print 'Warning: no dbm modules available for MessageInfoDB'
  270.             
  271.             self.dbm = None
  272.             self.db = None
  273.  
  274.  
  275.     
  276.     def __del__(self):
  277.         self.close()
  278.  
  279.     
  280.     def close(self):
  281.         
  282.         def noop():
  283.             pass
  284.  
  285.         getattr(self.db, 'close', noop)()
  286.         getattr(self.dbm, 'close', noop)()
  287.  
  288.     
  289.     def store(self):
  290.         if self.db is not None:
  291.             self.db.sync()
  292.         
  293.  
  294.  
  295.  
  296. try:
  297.     from persistent import Persistent
  298. except ImportError:
  299.     Persistent = object
  300.  
  301.  
  302. class _PersistentMessageInfo(MessageInfoBase, Persistent):
  303.     
  304.     def __init__(self):
  305.         import ZODB
  306.         OOBTree = OOBTree
  307.         import BTrees.OOBTree
  308.         MessageInfoBase.__init__(self)
  309.         self.db = OOBTree()
  310.  
  311.  
  312.  
  313. class MessageInfoZODB(storage.ZODBClassifier):
  314.     ClassifierClass = _PersistentMessageInfo
  315.     
  316.     def __init__(self, db_name, mode = 'c'):
  317.         self.nham = self.nspam = 0
  318.         storage.ZODBClassifier.__init__(self, db_name, mode)
  319.         self.classifier.store = self.store
  320.         self.db = self.classifier
  321.  
  322.     
  323.     def __setattr__(self, att, value):
  324.         object.__setattr__(self, att, value)
  325.  
  326.  
  327. _storage_types = {
  328.     'dbm': (MessageInfoDB, True, True),
  329.     'pickle': (MessageInfoPickle, False, True),
  330.     'zodb': (MessageInfoZODB, True, True) }
  331.  
  332. def open_storage(data_source_name, db_type = 'dbm', mode = None):
  333.     '''Return a storage object appropriate to the given parameters.'''
  334.     
  335.     try:
  336.         (klass, supports_mode, unused) = _storage_types[db_type]
  337.     except KeyError:
  338.         raise storage.NoSuchClassifierError(db_type)
  339.  
  340.     if supports_mode and mode is not None:
  341.         return klass(data_source_name, mode)
  342.     else:
  343.         return klass(data_source_name)
  344.  
  345.  
  346. def database_type():
  347.     dn = ('Storage', 'messageinfo_storage_file')
  348.     (nm, typ) = storage.database_type((), default_name = dn)
  349.     if typ not in _storage_types.keys():
  350.         typ = 'pickle'
  351.     
  352.     return (nm, typ)
  353.  
  354.  
  355. class Message(object, email.Message.Message):
  356.     '''An email.Message.Message extended for SpamBayes'''
  357.     
  358.     def __init__(self, id = None):
  359.         email.Message.Message.__init__(self)
  360.         self.stored_attributes = [
  361.             'c',
  362.             't',
  363.             'date_modified']
  364.         self.getDBKey = self.getId
  365.         self.id = None
  366.         self.c = None
  367.         self.t = None
  368.         self.date_modified = None
  369.         if id is not None:
  370.             self.setId(id)
  371.         
  372.  
  373.     _message_info_db = None
  374.     
  375.     def _get_class_message_info_db(klass):
  376.         if klass._message_info_db is None:
  377.             (nm, typ) = database_type()
  378.             klass._message_info_db = open_storage(nm, typ)
  379.         
  380.         return klass._message_info_db
  381.  
  382.     _get_class_message_info_db = classmethod(_get_class_message_info_db)
  383.     
  384.     def _set_class_message_info_db(klass, value):
  385.         klass._message_info_db = value
  386.  
  387.     _set_class_message_info_db = classmethod(_set_class_message_info_db)
  388.     
  389.     def _get_message_info_db(self):
  390.         return self._get_class_message_info_db()
  391.  
  392.     
  393.     def _set_message_info_db(self, value):
  394.         self._set_class_message_info_db(value)
  395.  
  396.     message_info_db = property(_get_message_info_db, _set_message_info_db)
  397.     
  398.     def setPayload(self, payload):
  399.         """DEPRECATED.
  400.  
  401.         This function does not work (as a result of using private
  402.         methods in a hackish way) in Python 2.4, so is now deprecated.
  403.         Use *_from_string as described above.
  404.  
  405.         More: Python 2.4 has a new email package, and the private functions
  406.         are gone.  So this won't even work.  We have to do something to
  407.         get this to work, for the 1.0.x branch, so use a different ugly
  408.         hack.
  409.         """
  410.         warnings.warn('setPayload is deprecated. Use email.message_from_string(payload, _class=Message) instead.', DeprecationWarning, 2)
  411.         new_me = email.message_from_string(payload, _class = Message)
  412.         self.__dict__.update(new_me.__dict__)
  413.  
  414.     
  415.     def setId(self, id):
  416.         if self.id and self.id != id:
  417.             raise ValueError, 'MsgId has already been set, cannot be changed' + `self.id` + `id`
  418.         
  419.         if id is None:
  420.             raise ValueError, 'MsgId must not be None'
  421.         
  422.         if type(id) not in types.StringTypes:
  423.             raise TypeError, 'Id must be a string'
  424.         
  425.         if id == STATS_START_KEY:
  426.             raise ValueError, 'MsgId must not be' + STATS_START_KEY
  427.         
  428.         self.id = id
  429.         self.message_info_db.load_msg(self)
  430.  
  431.     
  432.     def getId(self):
  433.         return self.id
  434.  
  435.     
  436.     def tokenize(self):
  437.         return tokenize(self)
  438.  
  439.     
  440.     def _force_CRLF(self, data):
  441.         '''Make sure data uses CRLF for line termination.'''
  442.         return CRLF_RE.sub('\r\n', data)
  443.  
  444.     
  445.     def as_string(self, unixfrom = False, mangle_from_ = True):
  446.         
  447.         try:
  448.             fp = StringIO.StringIO()
  449.             g = email.Generator.Generator(fp, mangle_from_ = mangle_from_)
  450.             g.flatten(self, unixfrom)
  451.             return self._force_CRLF(fp.getvalue())
  452.         except TypeError:
  453.             parts = []
  454.             for part in self.get_payload():
  455.                 parts.append(email.Message.Message.as_string(part, unixfrom))
  456.             
  457.             return self._force_CRLF('\n'.join(parts))
  458.  
  459.  
  460.     
  461.     def modified(self):
  462.         if self.id:
  463.             self.message_info_db.store_msg(self)
  464.         
  465.  
  466.     
  467.     def GetClassification(self):
  468.         if self.c == PERSISTENT_SPAM_STRING:
  469.             return options[('Headers', 'header_spam_string')]
  470.         elif self.c == PERSISTENT_HAM_STRING:
  471.             return options[('Headers', 'header_ham_string')]
  472.         elif self.c == PERSISTENT_UNSURE_STRING:
  473.             return options[('Headers', 'header_unsure_string')]
  474.         
  475.  
  476.     
  477.     def RememberClassification(self, cls):
  478.         if cls == options[('Headers', 'header_spam_string')]:
  479.             self.c = PERSISTENT_SPAM_STRING
  480.         elif cls == options[('Headers', 'header_ham_string')]:
  481.             self.c = PERSISTENT_HAM_STRING
  482.         elif cls == options[('Headers', 'header_unsure_string')]:
  483.             self.c = PERSISTENT_UNSURE_STRING
  484.         else:
  485.             raise ValueError, 'Classification must match header strings in options'
  486.         self.modified()
  487.  
  488.     
  489.     def GetTrained(self):
  490.         return self.t
  491.  
  492.     
  493.     def RememberTrained(self, isSpam):
  494.         self.t = isSpam
  495.         self.modified()
  496.  
  497.     
  498.     def __repr__(self):
  499.         return 'spambayes.message.Message%r' % repr(self.__getstate__())
  500.  
  501.     
  502.     def __getstate__(self):
  503.         return (self.id, self.c, self.t)
  504.  
  505.     
  506.     def __setstate__(self, t):
  507.         (self.id, self.c, self.t) = t
  508.  
  509.  
  510.  
  511. class SBHeaderMessage(Message):
  512.     '''Message class that is cognizant of SpamBayes headers.
  513.     Adds routines to add/remove headers for SpamBayes'''
  514.     
  515.     def setPayload(self, payload):
  516.         '''DEPRECATED.
  517.         '''
  518.         warnings.warn('setPayload is deprecated. Use email.message_from_string(payload, _class=SBHeaderMessage) instead.', DeprecationWarning, 2)
  519.         new_me = email.message_from_string(payload, _class = SBHeaderMessage)
  520.         self.__dict__.update(new_me.__dict__)
  521.  
  522.     
  523.     def setIdFromPayload(self):
  524.         
  525.         try:
  526.             self.setId(self[options[('Headers', 'mailid_header_name')]])
  527.         except ValueError:
  528.             return None
  529.  
  530.         return self.id
  531.  
  532.     
  533.     def setDisposition(self, prob):
  534.         if prob < options[('Categorization', 'ham_cutoff')]:
  535.             disposition = options[('Headers', 'header_ham_string')]
  536.         elif prob > options[('Categorization', 'spam_cutoff')]:
  537.             disposition = options[('Headers', 'header_spam_string')]
  538.         else:
  539.             disposition = options[('Headers', 'header_unsure_string')]
  540.         self.RememberClassification(disposition)
  541.  
  542.     
  543.     def addSBHeaders(self, prob, clues):
  544.         """Add hammie header, and remember message's classification.  Also,
  545.         add optional headers if needed."""
  546.         self.setDisposition(prob)
  547.         disposition = self.GetClassification()
  548.         self[options[('Headers', 'classification_header_name')]] = disposition
  549.         if options[('Headers', 'include_score')]:
  550.             disp = '%.*f' % (options[('Headers', 'header_score_digits')], prob)
  551.             if options[('Headers', 'header_score_logarithm')]:
  552.                 if prob <= 0.0050000000000000001 and prob > 0.0:
  553.                     x = -math.log10(prob)
  554.                     disp += ' (%d)' % x
  555.                 
  556.                 if prob >= 0.995 and prob < 1.0:
  557.                     x = -math.log10(1.0 - prob)
  558.                     disp += ' (%d)' % x
  559.                 
  560.             
  561.             self[options[('Headers', 'score_header_name')]] = disp
  562.         
  563.         if options[('Headers', 'include_thermostat')]:
  564.             thermostat = '**********'
  565.             self[options[('Headers', 'thermostat_header_name')]] = thermostat[:int(prob * 10)]
  566.         
  567.         if options[('Headers', 'include_evidence')]:
  568.             hco = options[('Headers', 'clue_mailheader_cutoff')]
  569.             sco = 1 - hco
  570.             evd = []
  571.             for word, score in clues:
  572.                 if word == '*H*' and word == '*S*' and score <= hco or score >= sco:
  573.                     if isinstance(word, types.UnicodeType):
  574.                         word = email.Header.Header(word, charset = 'utf-8').encode()
  575.                     
  576.                     
  577.                     try:
  578.                         evd.append('%r: %.2f' % (word, score))
  579.                     except TypeError:
  580.                         evd.append('%r: %s' % (word, score))
  581.                     except:
  582.                         None<EXCEPTION MATCH>TypeError
  583.                     
  584.  
  585.                 None<EXCEPTION MATCH>TypeError
  586.             
  587.             wrappedEvd = []
  588.             headerName = options[('Headers', 'evidence_header_name')]
  589.             lineLength = len(headerName) + len(': ')
  590.             for component, index in zip(evd, range(len(evd))):
  591.                 wrappedEvd.append(component)
  592.                 lineLength += len(component)
  593.                 if index < len(evd) - 1:
  594.                     if lineLength + len('; ') + len(evd[index + 1]) < 78:
  595.                         wrappedEvd.append('; ')
  596.                     else:
  597.                         wrappedEvd.append(';\n\t')
  598.                         lineLength = 8
  599.                 lineLength + len('; ') + len(evd[index + 1]) < 78
  600.             
  601.             self[headerName] = ''.join(wrappedEvd)
  602.         
  603.         if options[('Headers', 'add_unique_id')]:
  604.             self[options[('Headers', 'mailid_header_name')]] = self.id
  605.         
  606.         self.addNotations()
  607.  
  608.     
  609.     def addNotations(self):
  610.         """Add the appropriate string to the subject: and/or to: header.
  611.  
  612.         This is a reasonably ugly method of including the classification,
  613.         but no-one has a better idea about how to allow filtering in
  614.         'stripped down' mailers (i.e. Outlook Express), so, for the moment,
  615.         this is it.
  616.         """
  617.         disposition = self.GetClassification()
  618.         self.notateTo(disposition)
  619.         self.notateSubject(disposition)
  620.  
  621.     
  622.     def notateTo(self, disposition):
  623.         if isinstance(options[('Headers', 'notate_to')], types.StringTypes):
  624.             notate_to = (options[('Headers', 'notate_to')],)
  625.         else:
  626.             notate_to = options[('Headers', 'notate_to')]
  627.         if disposition in notate_to:
  628.             address = '%s@spambayes.invalid' % (disposition,)
  629.             
  630.             try:
  631.                 self.replace_header('To', '%s,%s' % (address, self['To']))
  632.             except KeyError:
  633.                 self['To'] = address
  634.             except:
  635.                 None<EXCEPTION MATCH>KeyError
  636.             
  637.  
  638.         None<EXCEPTION MATCH>KeyError
  639.  
  640.     
  641.     def notateSubject(self, disposition):
  642.         if isinstance(options[('Headers', 'notate_subject')], types.StringTypes):
  643.             notate_subject = (options[('Headers', 'notate_subject')],)
  644.         else:
  645.             notate_subject = options[('Headers', 'notate_subject')]
  646.         if disposition in notate_subject:
  647.             
  648.             try:
  649.                 self.replace_header('Subject', '%s,%s' % (disposition, self['Subject']))
  650.             except KeyError:
  651.                 self['Subject'] = disposition
  652.             except:
  653.                 None<EXCEPTION MATCH>KeyError
  654.             
  655.  
  656.         None<EXCEPTION MATCH>KeyError
  657.  
  658.     
  659.     def delNotations(self):
  660.         """If present, remove our notation from the subject: and/or to:
  661.         header of the message.
  662.  
  663.         This is somewhat problematic, as we cannot be 100% positive that we
  664.         added the notation.  It's almost certain to be us with the to:
  665.         header, but someone else might have played with the subject:
  666.         header.  However, as long as the user doesn't turn this option on
  667.         and off, this will all work nicely.
  668.  
  669.         See also [ 848365 ] Remove subject annotations from message review
  670.                             page
  671.         """
  672.         subject = self['Subject']
  673.         if subject:
  674.             ham = options[('Headers', 'header_ham_string')] + ','
  675.             spam = options[('Headers', 'header_spam_string')] + ','
  676.             unsure = options[('Headers', 'header_unsure_string')] + ','
  677.             if options[('Headers', 'notate_subject')]:
  678.                 for disp in (ham, spam, unsure):
  679.                     if subject.startswith(disp):
  680.                         self.replace_header('Subject', subject[len(disp):])
  681.                         break
  682.                         continue
  683.                 
  684.             
  685.         
  686.         to = self['To']
  687.         if to:
  688.             ham = '%s@spambayes.invalid,' % (options[('Headers', 'header_ham_string')],)
  689.             spam = '%s@spambayes.invalid,' % (options[('Headers', 'header_spam_string')],)
  690.             unsure = '%s@spambayes.invalid,' % (options[('Headers', 'header_unsure_string')],)
  691.             if options[('Headers', 'notate_to')]:
  692.                 for disp in (ham, spam, unsure):
  693.                     if to.startswith(disp):
  694.                         self.replace_header('To', to[len(disp):])
  695.                         break
  696.                         continue
  697.                 
  698.             
  699.         
  700.  
  701.     
  702.     def currentSBHeaders(self):
  703.         '''Return a dictionary containing the current values of the
  704.         SpamBayes headers.  This can be used to restore the values
  705.         after using the delSBHeaders() function.'''
  706.         headers = { }
  707.         for header_name in [
  708.             options[('Headers', 'classification_header_name')],
  709.             options[('Headers', 'mailid_header_name')],
  710.             options[('Headers', 'classification_header_name')] + '-ID',
  711.             options[('Headers', 'thermostat_header_name')],
  712.             options[('Headers', 'evidence_header_name')],
  713.             options[('Headers', 'score_header_name')],
  714.             options[('Headers', 'trained_header_name')]]:
  715.             value = self[header_name]
  716.             if value is not None:
  717.                 headers[header_name] = value
  718.                 continue
  719.         
  720.         return headers
  721.  
  722.     
  723.     def delSBHeaders(self):
  724.         del self[options[('Headers', 'classification_header_name')]]
  725.         del self[options[('Headers', 'mailid_header_name')]]
  726.         del self[options[('Headers', 'classification_header_name')] + '-ID']
  727.         del self[options[('Headers', 'thermostat_header_name')]]
  728.         del self[options[('Headers', 'evidence_header_name')]]
  729.         del self[options[('Headers', 'score_header_name')]]
  730.         del self[options[('Headers', 'trained_header_name')]]
  731.         self.delNotations()
  732.  
  733.  
  734.  
  735. def insert_exception_header(string_msg, msg_id = None):
  736.     '''Insert an exception header into the given RFC822 message (as text).
  737.  
  738.     Returns a tuple of the new message text and the exception details.'''
  739.     stream = StringIO.StringIO()
  740.     traceback.print_exc(None, stream)
  741.     details = stream.getvalue()
  742.     detailLines = details.strip().split('\n')
  743.     dottedDetails = '\n.'.join(detailLines)
  744.     headerName = 'X-Spambayes-Exception'
  745.     header = email.Header.Header(dottedDetails, header_name = headerName)
  746.     
  747.     try:
  748.         (headers, body) = re.split('\\n\\r?\\n', string_msg, 1)
  749.     except ValueError:
  750.         headers = string_msg
  751.         body = ''
  752.  
  753.     header = re.sub('\\r?\\n', '\r\n', str(header))
  754.     headers += '\n%s: %s\r\n' % (headerName, header)
  755.     if msg_id:
  756.         headers += '%s: %s\r\n' % (options[('Headers', 'mailid_header_name')], msg_id)
  757.     
  758.     return (headers + '\r\n' + body, details)
  759.  
  760.